package com.springboot.middleware.middleware_lock.server.service;

import com.springboot.middleware.middleware_lock.model.entity.UserReg;
import com.springboot.middleware.middleware_lock.model.mapper.UserRegMapper;
import com.springboot.middleware.middleware_lock.server.dto.UserRegDto;
import com.springboot.middleware.middleware_lock.server.utils.RedisUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class UserRegServiceImpl implements UserRegService{
    private static final Logger log = LoggerFactory.getLogger(UserRegServiceImpl.class);
    //ZooKeeper分布式锁的实现原理是有ZNode节点的创建与删除跟监听机制构成的
    //而ZNode节点将对应一个具体的路径, 与Unix文件夹路径类似, 需要以/开头
    private static final String pathPrefix = "/middleware/zkLock/";

    @Autowired
    private UserRegMapper userRegMapper;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private CuratorFramework client;

    /**
     * 处理用户提交注册-不带锁
     *
     * @param dto UserRegDto(用户名, 密码)
     * @throws Exception 账户已注册
     */
    @Override
    public void userRegNoLock(UserRegDto dto) throws Exception {
        UserReg userReg = userRegMapper.selectByUserName(dto.getUserName());
        if (userReg == null) {
            log.info("---不加分布式锁---, 当前用户名为:{}", dto.getUserName());
            UserReg entity = new UserReg();
            BeanUtils.copyProperties(dto, entity);
            entity.setCreateTime(new Date());
            userRegMapper.insertSelective(entity);
        } else {
            throw new Exception("用户信息已经存在");
        }
    }

    /**
     * 处理用户提交注册-基于Redis的分布式锁
     *
     * @param dto UserRegDto(用户名, 密码)
     * @throws Exception 账户已注册
     */
    @Override
    public void UserRegWithLock(UserRegDto dto) throws Exception {
        //精心设计并构造SETNX中的key, 一定要跟实际的业务或共享资源挂钩
        final String key = dto.getUserName() + "-lock";
        //设计key对应的value应该具有随机性
        final String value = System.nanoTime() + "" + UUID.randomUUID();
        //调用SETNX操作获取锁, 如果返回true, 代表获取锁成功
        boolean res = redisUtils.setIfAbsent(key, value);
        if (res) {
            //为了防止出现死锁, 加上EXPIRE操作, 即key的过期时间, 在这里设置为20s, 实际开发是情况而定
            redisUtils.expire(key, 20L, TimeUnit.SECONDS);
            try {
                UserReg userReg = userRegMapper.selectByUserName(dto.getUserName());
                if (userReg == null) {
                    log.info("---加分布式锁---, 当前用户名为:{}", dto.getUserName());
                    UserReg entity = new UserReg();
                    BeanUtils.copyProperties(dto, entity);
                    entity.setCreateTime(new Date());
                    userRegMapper.insertSelective(entity);
                }
            } catch (Exception e) {
                throw e;
            } finally {
                //不管发生任何状况, 都需要在redis加锁成功并访问操作完成共享资源后释放资源
                if (value.equals(redisUtils.get(key).toString())) {
                    redisUtils.del(key);
                }
            }
        }
    }

    /**
     * 处理用户提交注册-基于ZooKeeper的分布式锁
     *
     * @param dto UserRegDto(用户名, 密码)
     * @throws Exception 获取ZooKeeper锁失败 / 账户已注册
     */
    @Override
    public void UserRegWithZKLock(UserRegDto dto) throws Exception {
        //构造ZooKeeper互斥锁组件实例 , 需要将监控用的客户端实例和共享资源作为构造参数
        InterProcessMutex mutex = new InterProcessMutex(client, pathPrefix + dto.getUserName() + "-lock");
        try {
            /* 采用互斥锁组件尝试获取分布式锁, 其中尝试的最大时间在这里设置为10s, 实际情况按需设定 */
            if (mutex.acquire(10L, TimeUnit.SECONDS)) {
                UserReg userReg = userRegMapper.selectByUserName(dto.getUserName());
                if (userReg == null) {
                    log.info("---zookeeper加分布式锁---, 当前用户名为:{}", dto.getUserName());
                    UserReg entity = new UserReg();
                    BeanUtils.copyProperties(dto, entity);
                    entity.setCreateTime(new Date());
                    userRegMapper.insertSelective(entity);
                } else {
                    throw new Exception("用户已存在");
                }
            } else {
                throw new Exception("获取ZooKeeper锁失败!");
            }
        } catch (Exception e){
            throw e;
        } finally {
            //不管发生任何事情, 都要释放该资源
            mutex.release();
        }
    }
}
